#import "FRecalcTypes.h"
#import "Vector.h"
#import "FFastAlgorithm.h"
#import "DrawingData.h"
/*
Copyright 2007 Julian Asamer

This file is part of Fractalicious.

    Fractalicious is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Fractalicious is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Fractalicious.  If not, see <http://www.gnu.org/licenses/>.
*/
	/*
		Ladies and gentlemen, behold.
		Within the following few hundred lines of code lies the secret of Fractalicious.
		It is the reason why it is so stunning fast.
		
		Nov. 18: removed last bugs
	*/

//This is the fast algorithm. (v. 3.0 in Fractalicious; v. 15.0 or something in general.)

//Little sidenote: This is kind of "back to my roots". In the fifth form I already implemented an 
//iterative fractal calculation algorithm, although the math behind changed.

//Sidenote after testing: WOOOT!!! This stuff is about 20x faster then v. 2.0 - a calculation of tousands of lines in less than 0.03sec!

//Nov, 15 '07: The iterative nonmatrixbased algorithmus is finally working. That was a tough one...

//General settings
//The VariableType - could also, for example, be set to double
#define VT float

//Array access macros
#define PX(points,i) (points[(i)*4    ])
#define PY(points,i) (points[(i)*4 + 1])
#define QX(points,i) (points[(i)*4 + 2])
#define QY(points,i) (points[(i)*4 + 3])

#define SET(points,i,line) \
	PX(points,i) = line->p->x; \
	PY(points,i) = line->p->y; \
	QX(points,i) = line->q->x; \
	QY(points,i) = line->q->y; 

//Array creation macros
#define CREATE_LINEARRAY(depth) malloc((sizeof (VT)) * depth * 4)
#define CREATE_POINTERARRAY(depth) malloc((sizeof (void *)) * depth)
#define CREATE_INTARRAY(depth) malloc(sizeof(int) * depth)

//Calculation macros

//Transforms the ith line in source with the transformerdata t1, o1, t2 and o2 to a line which is saved in target at the jth index.
#define TRANSFORM(source, i, target, j, ox, oy, t1, o1, t2, o2) \
		target[(j)*4    ] = source[(i)*4    ] + (ox)*(t1) - (oy)*(o1); \
		target[(j)*4 + 1] = source[(i)*4 + 1] + (oy)*(t1) + (ox)*(o1); \
		target[(j)*4 + 2] = source[(i)*4    ] + (ox)*(t2) - (oy)*(o2); \
		target[(j)*4 + 3] = source[(i)*4 + 1] + (oy)*(t2) + (ox)*(o2);

//Util macros
#define FORCE_IN_RANGE(var,min,max) ((var<min) ? min : ((var > max) ? max : var))

DrawableFractal * calculate(RecalcType type, NSMutableArray *startLines, NSMutableArray *transformers) {
	
	float end_length;
	if (type == Drawonly)
		return;
	if (type == Fast)
		end_length = 4;
	if (type == Full || type == Refine) 
		end_length = 1;
	DrawableFractal * drawData;
	
	if ([transformers count]==1 && [startLines count]==1) { //For all fractals with only one transformer, like the Spiral.
		int depth = calc_depth(
				[((Line2D *)[startLines objectAtIndex: 0]) length], 
				[[((Transformer2D *)[transformers objectAtIndex: 0]) transformLine: ((Line2D *)[startLines objectAtIndex: 0])] length], 
				end_length
		);
		depth = FORCE_IN_RANGE(depth, 2, 5000);


		VT *points = CREATE_LINEARRAY(depth);
		if (points==NULL) {
			return;
		}
		
		Line2D *l = ((Line2D *)[startLines objectAtIndex: 0]);
		SET(points, 0, l)

		Transformer2D *t = ((Transformer2D *)[transformers objectAtIndex: 0]);
		float t1 = t->t1;
		float o1 = t->o1;
		float t2 = t->t2;
		float o2 = t->o2;
		
		int i;
		
		for (i=1; i<depth; i+=1) {

			float ox = QX(points,i-1) - PX(points, i-1);
			float oy = QY(points,i-1) - PY(points, i-1);
        	
			TRANSFORM(points, i-1, points, i, ox, oy, t1, o1, t2, o2);
		}
		return [[Drawable_1s1t alloc] initWithPoints: points depth: depth];
	}

	if ([transformers count]>1 && [startLines count]==1) { //For all fractals with only multiple transformers but one startline, like the Tree/Fern/Koch.
		//Depth is not so trivial here any more, but still simple - we just use the transformer with the biggest proportion.
		int i,j,k,l;
		float longest = 0;
		for (i=0; i<[transformers count]; i++) {
			float a = [[((Transformer2D *)[transformers objectAtIndex: i]) 
				transformLine: ((Line2D *)[startLines objectAtIndex: 0])] length];
			if (longest < a)
				longest = a;
		}
		int depth = calc_depth(
				[((Line2D *)[startLines objectAtIndex: 0]) length], 
				longest, 
				end_length
		);
		if (depth<2) depth = 200;
		depth = FORCE_IN_RANGE(depth,2,1000);
		
		VT **points = CREATE_POINTERARRAY(depth);
		int *ls = CREATE_INTARRAY(depth);
		
		if (points==NULL||ls==NULL) {
			NSLog(@"Failed to alloc points/ls with depth: %i",depth);
			return;
		}
		
		//Extract Linecoords:
		VT * points0 = CREATE_LINEARRAY(1);
		Line2D *line = ((Line2D *)[startLines objectAtIndex: 0]);
		SET(points0, 0, line) 
		points[0] = points0;

		//Extract Transformerdata.
		int tcount = [transformers count];
		float * tdata = malloc(sizeof(float) * 4 * tcount);
		BOOL * needsCalc = malloc(sizeof(BOOL)*tcount);
		if (needsCalc==NULL) {
			NSLog(@"needsCalc = NULL, returning");
			return;
		}
		
		for (i=0; i<tcount; i++) {
			needsCalc[i] = YES;
			Transformer2D *t = ((Transformer2D *)[transformers objectAtIndex: i]);
			tdata[i*4    ] = t->t1;
			tdata[i*4 + 1] = t->o1;
			tdata[i*4 + 2] = t->t2;
			tdata[i*4 + 3] = t->o2;
		}
		
		ls[0]=1;
		int neededSize=1;
		
		int all=1; //Could probably be beautified somehow.
		for (i=1; i<depth; i++) {
			ls[i] = neededSize*tcount;
			all+=ls[i];
			if (all>10000) { //A maximum of >40.000 coordinates - on the iTouch/iPhone!
				for (j=i; j<depth; j++) {
					ls[j]  = 0;
					VT *acpoint = malloc(0);
					points[j]=acpoint;
				}
				break; break;
			}
			VT *acpoint = CREATE_LINEARRAY(ls[i]); //malloc(sizeof(float) * 4 * ls[i]);
			if (acpoint==NULL) {
				NSLog(@"Couldn't allocate acPoint.");
				return;
			}
			l = 0;
			for (j=0; j<ls[i-1];j++) {	
				if (needsCalc[j]) {
					float ox = points[i-1][j*4 + 2] - points[i-1][j*4];
					float oy = points[i-1][j*4 + 3] - points[i-1][j*4 + 1];
					for (k=0; k<tcount; k++) {
						
						TRANSFORM(points[i-1], j, acpoint, l, ox, oy, tdata[k*4], tdata[(k*4+1)], tdata[(k*4+2)], tdata[(k*4+3)])
						l++;
					}	
				}
			}
			points[i]=acpoint;
			neededSize = ls[i];
			needsCalc = realloc(needsCalc,sizeof(BOOL)*ls[i]);
			if (needsCalc == NULL) {
				NSLog(@"NeedsCalc could not be reallocated, returning");
				return;
			}
			for (j=0; j<ls[i]; j++) {
				if (end_length<sqrt(pow(points[i][j*4+2] - points[i][j*4],2)+pow(points[i][j*4+3] - points[i][j*4+1],2)))
					needsCalc[j]=YES;
				else {
					neededSize--;
					needsCalc[j]=NO;
				}
			}
		}
		free(needsCalc);
		
		return [[Drawable_1sxt alloc] initWithPoints: points depths: ls depthsdepth: depth];
	}
	//xsxt coming soon - nothing to worry about...
	return [[Placeholder alloc] init];
}


int calc_depth(float start_length, float rec1_length, float end_length) {
	////NSLog(@"Values: %f / %f / %f", end_length, start_length, rec1_length);
	if (end_length <= 0 || start_length <=0 || rec1_length <= 0) {
		return 0;
	}
	return 	ceil(log(end_length/start_length)/log(rec1_length/start_length));
}